﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CloudStorageLight.Core.Recommender
{
    /// <summary>
    /// リコメンドエンジン
    /// </summary>
    [Serializable]
    public class RecommendEngine
    {
        private RecommendTermModel model;
        private Matrix _weightMatrix = null;
        private Matrix _featureMatrix = null;
        private int featureCount = 20;
        private int LoopCount = 20;
        private List<IRecommendItem> articles;


        public RecommendEngine()
        {

        }

        public RecommendEngine(IEnumerable<IRecommendItem> arts, int featureCount = 20, int LoopCount = 20)
        {
            this.articles = arts.ToList();
            this.featureCount = featureCount;
            this.LoopCount = LoopCount;
            model = new RecommendTermModel();
            foreach (var art in arts)
            {
                EntryArticle(art);
            }
            var nnmf = model.GetNormalised();
            nnmf.Factorise(featureCount, LoopCount, out _weightMatrix, out _featureMatrix);
        }


        private void EntryArticle(IRecommendItem art)
        {

            var doc = model.AddRecommend(art.Id);
            foreach (var item in art.Keywords)
            {
                model.AddTerm(doc, item.Key, (uint)item.Value);
            }
        }

        private float CalcCorrelation(IRecommendItem article1, IRecommendItem article2)
        {
            var docIdx1 = model.GetRecommendIndex(article1.Id);
            var docIdx2 = model.GetRecommendIndex(article2.Id);
            float score = 0;
            for (int i = 0; i < _weightMatrix.ShapeX; i++)
            {
                score += _weightMatrix[i, docIdx1] * _weightMatrix[i, docIdx2];
            }
            return score;
        }

        public float CalcCorrelation(Dictionary<string, float> keywords, IRecommendItem article2)
        {
            var topic = new float[_featureMatrix.ShapeY];
            var total = keywords.Sum(x => x.Value);
            foreach (var keyword in keywords)
            {
                var keywordIndex = model.GetTermIndex(keyword.Key);

                if (keywordIndex >= 0)
                {
                    var termFrequency = (double)keyword.Value / total;
                    double inverseDocumentFrequency = Math.Log(model.DocumentList.Count / model.KeywrordDocumentOccurence[keywordIndex].Count);
                    float weight = (float)(inverseDocumentFrequency * termFrequency);

                    for (int i = 0; i < _featureMatrix.ShapeY; i++)
                    {
                        topic[i] += _featureMatrix[keywordIndex, i] * weight;
                    }
                }
            }

            float score = 0;
            var docIdx2 = model.GetRecommendIndex(article2.Id);

            for (int i = 0; i < _weightMatrix.ShapeX; i++)
            {
                score += (topic[i] * _weightMatrix[i, docIdx2]);
            }
            return score;
        }


        public Dictionary<IRecommendItem, float> GetTopRecommend(Dictionary<string, float> keywords, int maxSize = 10)
        {
            var scores = new Dictionary<IRecommendItem, float>();
            foreach (var item in articles)
            {
                scores.Add(item, CalcCorrelation(keywords, item));
            }
            return TakeDictionary(scores, maxSize);
        }

        private Dictionary<IRecommendItem, float> TakeDictionary(Dictionary<IRecommendItem, float> scores, int maxSize)
        {
            var ret = new Dictionary<IRecommendItem, float>();
            foreach (var item in scores.OrderByDescending(x => x.Value).Take(maxSize))
            {
                ret.Add(item.Key, item.Value);
            }
            return ret;
        }
      
    }
}
